1   package org.apache.lucene.index;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.io.IOException;
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Random;
27  
28  import org.apache.lucene.util.TestUtil;
29  
30  /**
31   * MergePolicy that makes random decisions for testing.
32   */
33  public class MockRandomMergePolicy extends MergePolicy {
34    private final Random random;
35    boolean doNonBulkMerges = true;
36  
37    public MockRandomMergePolicy(Random random) {
38      // fork a private random, since we are called
39      // unpredictably from threads:
40      this.random = new Random(random.nextLong());
41    }
42    
43    /** 
44     * Set to true if sometimes readers to be merged should be wrapped in a FilterReader
45     * to mixup bulk merging.
46     */
47    public void setDoNonBulkMerges(boolean v) {
48      doNonBulkMerges = v;
49    }
50  
51    @Override
52    public MergeSpecification findMerges(MergeTrigger mergeTrigger, SegmentInfos segmentInfos, IndexWriter writer) {
53      MergeSpecification mergeSpec = null;
54      //System.out.println("MRMP: findMerges sis=" + segmentInfos);
55  
56      int numSegments = segmentInfos.size();
57  
58      List<SegmentCommitInfo> segments = new ArrayList<>();
59      final Collection<SegmentCommitInfo> merging = writer.getMergingSegments();
60  
61      for(SegmentCommitInfo sipc : segmentInfos) {
62        if (!merging.contains(sipc)) {
63          segments.add(sipc);
64        }
65      }
66  
67      numSegments = segments.size();
68  
69      if (numSegments > 1 && (numSegments > 30 || random.nextInt(5) == 3)) {
70  
71        Collections.shuffle(segments, random);
72  
73        // TODO: sometimes make more than 1 merge?
74        mergeSpec = new MergeSpecification();
75        final int segsToMerge = TestUtil.nextInt(random, 1, numSegments);
76        if (doNonBulkMerges && random.nextBoolean()) {
77          mergeSpec.add(new MockRandomOneMerge(segments.subList(0, segsToMerge),random.nextLong()));
78        } else {
79          mergeSpec.add(new OneMerge(segments.subList(0, segsToMerge)));
80        }
81      }
82  
83      return mergeSpec;
84    }
85  
86    @Override
87    public MergeSpecification findForcedMerges(
88         SegmentInfos segmentInfos, int maxSegmentCount, Map<SegmentCommitInfo,Boolean> segmentsToMerge, IndexWriter writer)
89      throws IOException {
90  
91      final List<SegmentCommitInfo> eligibleSegments = new ArrayList<>();
92      for(SegmentCommitInfo info : segmentInfos) {
93        if (segmentsToMerge.containsKey(info)) {
94          eligibleSegments.add(info);
95        }
96      }
97  
98      //System.out.println("MRMP: findMerges sis=" + segmentInfos + " eligible=" + eligibleSegments);
99      MergeSpecification mergeSpec = null;
100     if (eligibleSegments.size() > 1 || (eligibleSegments.size() == 1 && isMerged(segmentInfos, eligibleSegments.get(0), writer) == false)) {
101       mergeSpec = new MergeSpecification();
102       // Already shuffled having come out of a set but
103       // shuffle again for good measure:
104       Collections.shuffle(eligibleSegments, random);
105       int upto = 0;
106       while(upto < eligibleSegments.size()) {
107         int max = Math.min(10, eligibleSegments.size()-upto);
108         int inc = max <= 2 ? max : TestUtil.nextInt(random, 2, max);
109         if (doNonBulkMerges && random.nextBoolean()) {
110           mergeSpec.add(new MockRandomOneMerge(eligibleSegments.subList(upto, upto+inc), random.nextLong()));
111         } else {
112           mergeSpec.add(new OneMerge(eligibleSegments.subList(upto, upto+inc)));
113         }
114         upto += inc;
115       }
116     }
117 
118     if (mergeSpec != null) {
119       for(OneMerge merge : mergeSpec.merges) {
120         for(SegmentCommitInfo info : merge.segments) {
121           assert segmentsToMerge.containsKey(info);
122         }
123       }
124     }
125     return mergeSpec;
126   }
127 
128   @Override
129   public MergeSpecification findForcedDeletesMerges(SegmentInfos segmentInfos, IndexWriter writer) throws IOException {
130     return findMerges(null, segmentInfos, writer);
131   }
132 
133   @Override
134   public boolean useCompoundFile(SegmentInfos infos, SegmentCommitInfo mergedInfo, IndexWriter writer) throws IOException {
135     // 80% of the time we create CFS:
136     return random.nextInt(5) != 1;
137   }
138   
139   static class MockRandomOneMerge extends OneMerge {
140     final Random r;
141     ArrayList<CodecReader> readers;
142 
143     MockRandomOneMerge(List<SegmentCommitInfo> segments, long seed) {
144       super(segments);
145       r = new Random(seed);
146     }
147 
148     @Override
149     public List<CodecReader> getMergeReaders() throws IOException {
150       if (readers == null) {
151         readers = new ArrayList<CodecReader>(super.getMergeReaders());
152         for (int i = 0; i < readers.size(); i++) {
153           // wrap it (e.g. prevent bulk merge etc)
154           // TODO: cut this over to FilterCodecReader api, we can explicitly
155           // enable/disable bulk merge for portions of the index we want.
156           int thingToDo = r.nextInt(7);
157           if (thingToDo == 0) {
158             // simple no-op FilterReader
159             readers.set(i, SlowCodecReaderWrapper.wrap(new FilterLeafReader(readers.get(i))));
160           } else if (thingToDo == 1) {
161             // renumber fields
162             // NOTE: currently this only "blocks" bulk merges just by
163             // being a FilterReader. But it might find bugs elsewhere, 
164             // and maybe the situation can be improved in the future.
165             readers.set(i, SlowCodecReaderWrapper.wrap(new MismatchedLeafReader(readers.get(i), r)));
166           }
167           // otherwise, reader is unchanged
168         }
169       }
170       return readers;
171     }
172   }
173 }